[AWS IoT] MQTTを使用して、Lambdaからブラウザを更新する方法〜aws-iot-device-sdk(aws-iot-sdk-browser-bundle.js)を使用する場合〜
1 はじめに
AIソリューション部の平内(SIN)です。
サーバサイドの処理で、「コンテンツが変化した」などの理由で、ブラウザを更新したい場面は多いと思います。今回は、MQTTを使用して、Lambdaからブラウザをリロードしてみたいと思います。
なお、仕組みとしては、ブラウザ側のスクリプトでAWS IoTのエンドポイントをSubscribeしておき、Lambda側で何らかのメッセージをPublishします。 ブラウザ側のスクリプトは、メッセージを受け取ったタイミングで、ページをリロードするというものです。
ブラウザからのMQTTへ通信は、paho-mqttを利用したものが、比較的多く紹介されています。
<script src="https://cdnjs.cloudflare.com/ajax/libs/paho-mqtt/1.0.1/mqttws31.min.js"></script> const client = new Paho.MQTT.Client(endpoint, clientId);
しかし、今回は、aws-iot-sdk-browser-bundle.jsを使用して、下記のようにブラウザ上のJavascriptからaws-iot-device-sdkをrequireして使用する方法を試してみました。
<script> var awsIot = require('aws-iot-device-sdk'); const deviceIot = awsIot.device({ protocol: 'wss', port: 443, host: endpoint //・・・略・・・ }); // メッセージ到着 deviceIot.on('message', function(_topic, payload) { } // Subscribe deviceIot.subscribe(topic, undefined, function (err, granted) { } } </script>
2 EndPoint
MQTTを使用するためのEndpointは、AWS IoTで用意されています。リージョンごとにURLが決まっていますので、それをそのまま使用します。
Endpointへのアクセスは、ブラウザ側については、CognitoのPoolID、Lambda側は、LambdaのRoleにポリシーを追加することで行いますので、証明書の作成などは必要ありません。
3 Cognito(PoolIDの作成)
認証されていないIDに対してアクセスを有効にするにチェックを入れてPoolIDを作成します。
認証されていない時のロールに、下記のようなAWS IoTのトピックをSubscribeするための最低限のポリシーを追加します。ここでのPoolIDは、Javascriptに直書きされ公開されますので、ポリシーの制限には注意が必要です。
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "iot:Connect", "iot:Receive", "iot:Subscribe" ], "Resource": [ "arn:aws:iot:ap-northeast-1:xxxxxxxxxxxx:client/*", "arn:aws:iot:ap-northeast-1:xxxxxxxxxxxx:topic/Refresh_Topic", "arn:aws:iot:ap-northeast-1:xxxxxxxxxxxx:topicfilter/Refresh_Topic" ] } ] }
Resourceで指定されている topicfilter/ は、iot:Subscribe のためのもので、topic/ は、iot:Receiveのためです。
4 Credentialsの取得
AWS SDKでgetCredentialsForIdentity()を使用すると、PoolIDに与えられたパーミッションでAWSのリソースにアクセスするために、Credentials(アクセスキー・シークレット・トークンのセット)が取得可能です。これは、一時的に有効なもので、取得するたびに変化します。
なお、getCredentialsForIdentity()のパラメータに必要なIdentityIdは、getId()で取得します。こちらも、一時的なIDであり、ここにPoolIDをそんまま使用することはできません。
<br />const AWS = require('aws-sdk'); async function getCredentials() { AWS.config.region = region; var cognito = new AWS.CognitoIdentity(); var params = { IdentityPoolId: PoolId }; const identityId = await cognito.getId(params).promise(); const data = await cognito.getCredentialsForIdentity(identityId).promise(); var credentials = { accessKey: data.Credentials.AccessKeyId, secretKey: data.Credentials.SecretKey, sessionToken: data.Credentials.SessionToken }; return credentials; }
5 Subscribe
Credentialsを使用して、トピックをSubscribeするコードは、以下のようになります。 先に指定したポリシーでは、clientIdは、何でも大丈夫ですが、トピック名は、固定(Refresh_Topic)になります。
const awsIot = require('aws-iot-device-sdk'); const endpoint = 'xxxxxxxxxx-ats.iot.ap-northeast-1.amazonaws.com'; const clientId = 'client_id' const topic = "Refresh_Topic" async function job() { const credentials = await getCredentials(); // Credentialsの取得 const deviceIot = awsIot.device({ region: region, clientId: clientId, accessKeyId: credentials.accessKey, secretKey: credentials.secretKey, sessionToken : credentials.sessionToken, protocol: 'wss', port: 443, host: endpoint }); deviceIot.on('message', function(_topic, payload) { console.log('>' + payload.toString()); }); deviceIot.subscribe(topic, undefined, function (err, granted){ if( err ){ console.log('subscribe error: ' + err); } else { console.log('subscribe success'); } }); } job();
6 メッセージ受信の確認
TopicにPublishされたデータが、ちゃんと受信できているかどうか(Subscribeが正常に完了しているかどうか)は、AWSコンソールから簡単に行うことができます。
AWS IoTのテストでトピックへの発行を選択します。
トピック名を指定し、送信したいメッセージを入力してトピックへの発行を押すと、Subscribe側でメッセージが確認できます。
7 aws-iot-sdk-browser-bundle.js
ここまでの作業で、Cognitoを使用するために const AWS = require('aws-sdk'); とし、AWS IoTを使用するために、const awsIot = require('aws-iot-device-sdk'); と記述しました。
ブラウザから使用する場合、AWS SDKは、下記の用にロードできますが、これだけでは、残念ながらaws-iot-device-sdkは使用できません。
<script src="https://sdk.amazonaws.com/js/aws-sdk-2.283.1.min.js"></script>
AWS IoTを使用するために、今回、使用させて頂いたのは、https://github.com/aws/aws-iot-device-sdk-jsのBrowser Applicationsのところにある要領です。
下記の手順で、aws-iot-sdk-browser-bundle.jsというモジュールが作成できます。
$ git clone https://github.com/aws/aws-iot-device-sdk-js.git $ cd aws-iot-device-sdk-js $ npm install -g browserify $ npm run-script browserize $ ls browser/aws-iot-sdk-browser-bundle.js browser/aws-iot-sdk-browser-bundle.js
このaws-iot-sdk-browser-bundle.jsをhtmlと同じ場所にコピーしてロードすることで、require('aws-iot-device-sdk') と書けるようになります。
<script src="aws-iot-sdk-browser-bundle.js"></script>
8 html
最終的に完成したhtmlは、以下のとおりです。
このhtmlは、読み込まれた時間を表示するだけのページです。 AWS IoTのEndpointに対して、トピック名Refresh_Topic でSubscribeし、メッセージがポストされたら、ページをリロードしています。(時間が現在時にリフレッシュされます)
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Refresh Sample</title> <script src="aws-iot-sdk-browser-bundle.js"></script> <script src="https://sdk.amazonaws.com/js/aws-sdk-2.283.1.min.js"></script> </head> <body> <h1 id="time">DATETIME</h1> <script> var now = new Date(); document.getElementById("time").innerHTML = now.toLocaleTimeString(); var awsIot = require('aws-iot-device-sdk'); const PoolId = 'ap-northeast-1:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'; const region = 'ap-northeast-1'; async function getCredentials() { AWS.config.region = region; var cognitoidentity = new AWS.CognitoIdentity(); var params = { IdentityPoolId: PoolId }; const identityId = await cognitoidentity.getId(params).promise(); const data = await cognitoidentity.getCredentialsForIdentity(identityId).promise(); var credentials = { accessKey: data.Credentials.AccessKeyId, secretKey: data.Credentials.SecretKey, sessionToken: data.Credentials.SessionToken }; return credentials; } const topic = "Refresh_Topic"; async function job() { const endpoint = 'xxxxxxxxxxxx-ats.iot.ap-northeast-1.amazonaws.com'; const clientId = 'sample_id' const credentials = await getCredentials(); // Credentialsの取得 const deviceIot = awsIot.device({ region: region, clientId: clientId, accessKeyId: credentials.accessKey, secretKey: credentials.secretKey, sessionToken : credentials.sessionToken, protocol: 'wss', port: 443, host: endpoint }); deviceIot.on('message', function(_topic, payload) { console.log('> ' + payload.toString()); location.reload(); }); deviceIot.subscribe(topic, undefined, function (err, granted){ if( err ){ console.log('subscribe error: ' + err); } else { console.log('subscribe success'); } }); } job(); </script> </body> </html>
テストしている様子です。何らかのメッセージがPublishされると、ブラウザが更新されていることを確認できます。
9 Publish側のコード
最後に、Publish側のLambdaのコードを紹介させて頂きます。Lambdaには、AWS IoTへPublishできるポリシーを追加する必要があります。
const endpoint = 'xxxxxxxxxxxx-ats.iot.ap-northeast-1.amazonaws.com'; const topic = "Refresh_Topic" const aws = require('aws-sdk'); const region = 'ap-northeast-1'; var iotdata = new aws.IotData({ endpoint: endpoint, region: region }); var params = { topic: topic, payload: '{"action":"refresh"}', qos: 0 }; let result = await iotdata.publish(params).promise();
10 最後に
今回は、Lambdaからブラウザをリフレッシュする手段としてMQTTを使用しました。
ブラウザからのAWS IoTの利用も、今回紹介した、aws-iot-device-sdkを使用すれば、 比較的ハードルが下がるような気がしました。